package net.sf.xoperator.blackboard;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import net.sf.xoperator.Constants;
import net.sf.xoperator.configuration.KSConfiguration;
import net.sf.xoperator.rdfxmpp.SPARQLQuery;
import net.sf.xoperator.rdfxmpp.SPARQLResult;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.packet.Message;
import org.openbbs.blackboard.Blackboard;
import org.openbbs.blackboard.BlackboardAccess;
import org.openbbs.blackboard.BlackboardObserver;
import org.openbbs.blackboard.ExactZoneSelector;
import org.openbbs.blackboard.NamedZone;
import org.openbbs.blackboard.Zone;
import org.openbbs.blackboard.ZoneSelector;
import org.openbbs.blackboard.ZonedBlackboardAccess;
import org.openbbs.blackboard.filter.KindOfFilter;
import org.openbbs.blackboard.ks.KSExecutionContext;
import org.openbbs.blackboard.ks.KnowledgeSource;

/**
 * The all-in-one no plan all you need approach. everything to make the
 * blackbaord run is in this class. Just implement some Knowledgesources,
 * instantiate this class with them and you'll be fine. As long as every
 * Knowledge source can determine just from the content of the zone what if is
 * should perform its task or not. And also it has to know if already has
 * performed. Easy.
 * 
 * @author joerg
 * 
 */
public class NoPlanBlackBoardControl implements BlackboardObserver {

	private Log log = LogFactory.getLog(NoPlanBlackBoardControl.class);

	private Blackboard bboard;

	private List<KnowledgeSource> ksources = new ArrayList<KnowledgeSource>();

	public KSExecutionContext newZonedAccess() {
		final Zone nz = openRandomNamedZone();
		return new KSExecutionContext() {

			public BlackboardAccess blackboard() {

				return new ZonedBlackboardAccess(
						NoPlanBlackBoardControl.this.bboard, nz);
			}

			public BlackboardAccess blackboard(Zone zone) {

				return new ZonedBlackboardAccess(
						NoPlanBlackBoardControl.this.bboard, zone);
			}

		};
	}

	private Zone openRandomNamedZone() {
		final NamedZone nz = new NamedZone(System.currentTimeMillis()
				+ RandomStringUtils.randomAlphanumeric(8));
		this.bboard.openZone(nz);
		this.log.debug("opened a new zone on the blackboard: " + nz.name());
		return nz;
	}

	/**
	 * a special zone on the blackboard dedicated for keeping track of the
	 * configuration. Changes made to this context will not notify any of the
	 * knowledgesource, use <code>propagateConfiguration</code> instead.
	 * Updating this zone is done in the yoperator application by the
	 * <code>SErializingPersonalDAtastore</code> Knowledgesource
	 * 
	 * @return
	 */
	public KSExecutionContext getConfigurationContext() {
		return new KSExecutionContext() {

			public BlackboardAccess blackboard() {

				return new ZonedBlackboardAccess(
						NoPlanBlackBoardControl.this.bboard,
						Constants.CONFIGURATION_ZONE);
			}

			public BlackboardAccess blackboard(Zone zone) {

				return new ZonedBlackboardAccess(
						NoPlanBlackBoardControl.this.bboard, zone);
			}

		};
	}

	public NoPlanBlackBoardControl(Blackboard bboard) {
		super();
		this.bboard = bboard;
		bboard.openZone(Constants.CONFIGURATION_ZONE);

		bboard.registerInterest(new ZoneSelector() {
			public boolean selects(Zone zone) {
				if (zone.equals(Constants.CONFIGURATION_ZONE)) {
					return false;
				} else {
					return true;
				}

			}
		}, this);
	}

	public void registerKnowledgeSource(KnowledgeSource ks) {
		this.ksources.add(ks);
	}

	public void blackboardDidAddEntry(final Blackboard blackboard,
			final Zone zone, final Object entry) {
		this.log.debug("New Entry on Blackboard: " + entry.toString());
		KSExecutionContext context = new KSExecutionContext() {
			public BlackboardAccess blackboard() {

				return new ZonedBlackboardAccess(blackboard, zone);
			}

			public BlackboardAccess blackboard(Zone zo) {
				// TODO Auto-generated method stub
				return new ZonedBlackboardAccess(blackboard, zo);
			}
		};


			for (KnowledgeSource source : this.ksources) {
				if (source.isEnabled(context)) {
					source.execute(context);
				}

			}

	}

	public void blackboardDidRemoveEntry(Blackboard blackboard, Zone zone,
			Object entry) {

	}

	/**
	 * puts a message into the blackboard and registers the messages in the
	 * appropriate chat zone
	 * 
	 * @param message
	 * @param chat
	 */
	public void registerMessage(Message message, Chat chat) {
		Zone chatzone = new NamedZone(chat.getThreadID());

		if (!this.bboard.exists(new ExactZoneSelector(chatzone),
				new KindOfFilter(List.class))) {
			// create a zone for the chat
			this.bboard.openZone(chatzone);
			this.bboard.write(chatzone, Collections
					.synchronizedList(new ArrayList<Zone>()));
		}

		List<Zone> messagesOfChat = (List<Zone>) this.bboard.take(
				new ExactZoneSelector(chatzone), new KindOfFilter(List.class));
		Zone newMessageZone = openRandomNamedZone();
		messagesOfChat.add(newMessageZone);
		// the chat to which this message belongs is written to the blackboard,
		// to allow later referencing
		this.bboard.write(newMessageZone, chat);
		// and finally we write the message to the zone
		this.bboard.write(newMessageZone, message);

	}

	public Blackboard getBboard() {
		return this.bboard;
	}

	/**
	 * writes a configuration element to the blackboard on a new zone and
	 * deletes it afterwards. Thus
	 * 
	 * @param conf
	 */
	public void propagateConfiguration(KSConfiguration conf) {
		Zone tmpzone = openRandomNamedZone();
		this.bboard.write(tmpzone, conf);
		this.bboard.closeZone(tmpzone);
	}

	public Set<SPARQLResult> executeQuery(SPARQLQuery query,
			KSExecutionContext context) {

		return executeQuery(query, context, 0);
	}

	public Set<SPARQLResult> executeQuery(SPARQLQuery query,
			KSExecutionContext context, int waitForCollection) {
		Zone tmpzone = openRandomNamedZone();
		this.bboard.write(tmpzone, context.blackboard().read(
				new KindOfFilter(Chat.class)));
		this.bboard.write(tmpzone, query);
		if (waitForCollection > 0) {
			try {
				Thread.sleep(waitForCollection);
			} catch (InterruptedException e) {
				log.error("Error: cant sleep");
			}
		}

		Set resultset = this.bboard.readAll(new ExactZoneSelector(tmpzone),
				new KindOfFilter(SPARQLResult.class));

		this.bboard.closeZone(tmpzone);

		return resultset;
	}

	public Set<SPARQLResult> executeQuery(SPARQLQuery query) {
		Zone tmpzone = openRandomNamedZone();
		this.bboard.write(tmpzone, query);

		Set resultset = this.bboard.readAll(new ExactZoneSelector(tmpzone),
				new KindOfFilter(SPARQLResult.class));

		this.bboard.closeZone(tmpzone);

		return resultset;
	}

}
